#include "ParticleSystem.h"
#include "..\utils\Log.h"
#include "..\utils\GameMath.h"
#include "..\content\Shader.h"
#include "ParticleSystemDecl.h"

namespace ds {

BoxEmitter::BoxEmitter(uint32 width,uint32 height,uint32 counter,const Rect& textureRect,float ttl,float initialSize) 
	: ParticleEmitter(counter,textureRect,ttl,initialSize) , m_Width(width) , m_Height(height) {
	rand.seed(GetTickCount());
}

void BoxEmitter::createParticles(const Vec3& startPosition,Particles& particles) {
	float centerX = (startPosition.x + m_Width) * 0.5f;
	float centerY = (startPosition.y + m_Height) * 0.5f;
	particles.assureSize(m_Max);
	for ( uint32 i = 0 ; i < m_Max; ++i ) {
		Particle p;
		p.emitted = true;
		float x = startPosition.x + rand()*m_Width;
		float y = startPosition.y + rand()*m_Height;		
		p.position = Vec3(x,y,0.0f);
		p.initialPosition = p.position;
		p.timer = 0.0f;
		p.ttl = m_TTL;
		p.size = Vec3(m_Size,m_Size,m_Size);
		p.color = Color(1.0f,1.0f,1.0f,1.0f);
		p.alive = true;
		p.textureRect = m_TextureRect;		
		p.angle = ds::math::getTargetAngle(Vec2(centerX,centerY),Vec2(p.position.x,p.position.y));
		particles.append(p);
	}	
}


RingEmitter::RingEmitter(float radius,uint32 counter,const Rect& textureRect,float ttl,float initialSize,const Color& color,bool rotate) 
	: ParticleEmitter(counter,textureRect,ttl,initialSize) , m_Radius(radius) , m_Color(color) , m_Rotate(rotate) {
		rand.seed(GetTickCount());
}

void RingEmitter::createParticles(const Vec3& startPosition,Particles& particles) {
	float angle = 0.0f;
	float add = 360.0f / (float)m_Max;
	particles.assureSize(m_Max);
	for ( uint32 i = 0; i < m_Max;++i ) {
		Particle p;
		p.emitted = true;
		p.alive = true;
		p.textureRect = m_TextureRect;		
		p.timer = 0.0f;
		p.size = Vec3(m_Size,m_Size,m_Size);
		p.ttl = m_TTL;
		p.color = m_Color;
		float xp = startPosition.x + cos(DEGTORAD(angle)) * m_Radius;
		float yp = startPosition.y + sin(DEGTORAD(angle)) * m_Radius;
		p.position = Vec3(xp,yp,0.0f);		
		if ( m_Rotate ) {
			p.angle = DEGTORAD(angle);
		}
		else {
			p.angle = 0.0f;
		}
		p.initialPosition = p.position;
		angle += add;
		particles.append(p);		
	}
}

PointEmitter::PointEmitter(uint32 counter,const Rect& textureRect,float ttl,float initialSize,const Color& color,bool rotate) 
	: ParticleEmitter(counter,textureRect,ttl,initialSize) , m_Color(color) , m_Rotate(rotate) {
		rand.seed(GetTickCount());
}

void PointEmitter::createParticles(const Vec3& startPosition,Particles& particles) {
	float angle = 0.0f;
	float add = 360.0f / (float)m_Max;	
	particles.assureSize(m_Max);
	for ( uint32 i = 0; i < m_Max;++i ) {
		Particle p;
		p.alive = true;
		p.emitted = true;
		p.textureRect = m_TextureRect;		
		p.timer = 0.0f;
		p.size = Vec3(m_Size,m_Size,m_Size);
		p.ttl = m_TTL;
		p.color = m_Color;
		p.position = startPosition;
		p.initialPosition = startPosition;		
		if ( m_Rotate ) {
			p.angle = DEGTORAD(angle);
		}
		else {
			p.angle = 0.0f;
		}
		angle += add;		
		particles.append(p);		
	}
}
// -----------------------------------------------
//  LifetimeAffector
// -----------------------------------------------
void LifetimeAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		p->timer += elapsed;
		if ( p->timer > p->ttl ) {
			p->alive = false;
		}
	}
}

// -----------------------------------------------
//  RotationAffector
// -----------------------------------------------
void RotationAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->alive ) {
			float normTime = p->timer / p->ttl;
			p->angle = normTime * DEGTORAD(360.0f) * m_Frequency;
		}
	}
}

// -----------------------------------------------
//  PositionAffector
// -----------------------------------------------
void PositionAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->alive ) {
			p->position.x = p->initialPosition.x + p->velocity.x * p->timer;
			p->position.y = p->initialPosition.y + p->velocity.y * p->timer;
		}
	}
}

// -----------------------------------------------
//  RadialVelocityAffector
// -----------------------------------------------
void RadialVelocityAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->emitted ) {
			float vel = m_Velocity;		
			if ( m_VelocityDiff != 0.0f ) {
				vel = (m_Velocity - m_VelocityDiff) + rand()*m_VelocityDiff;
			}
			p->radialVelocity = vel;
			Vec2 velo = math::getRadialVelocity(RADTODEG(p->angle),vel);
			p->velocity = Vec3(velo.x,velo.y,0.0f);
		}
		if ( p->alive ) {
			//nit->position += nit->velocity * elapsed;
			p->position.x = p->initialPosition.x + p->velocity.x * p->timer;
			p->position.y = p->initialPosition.y + p->velocity.y * p->timer;
		}
	}
}

// -----------------------------------------------
//  WiggleAffector
// -----------------------------------------------
void WiggleAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->emitted ) {
			p->radius = m_Min + rand() * (m_Max - m_Min);
		}
		if ( p->alive ) {					
			Vec2 pp = Vec2(p->position.x,p->position.y);
			Vec2 np = pp;
			np.x += p->velocity.x * elapsed;
			np.y += p->velocity.y * elapsed;

			Vec2 diff = np - pp;
			Vec2 perp = Vec2(diff.y,-diff.x);
			perp.unit();
			float t = p->timer / p->ttl * DEGTORAD(360.0f);
			float offset = p->radius * sin(p->angle + t * m_Amplitude);
			Vec2 sum = perp * offset;
			p->position.x += sum.x;
			p->position.y += sum.y;			
		}
	}
}

// -----------------------------------------------
//  AlphaAffector
// -----------------------------------------------
void AlphaAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->alive ) {
			float normTime = p->timer / p->ttl;
			p->color.a = m_Min + ( m_Max - m_Min ) * normTime;
		}
	}
}

// -----------------------------------------------
//  ColorFadeAffector
// -----------------------------------------------
void ColorFadeAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->alive ) {
			float normTime = p->timer / p->ttl;
			p->color.r = m_rMin + ( m_rMax - m_rMin ) * normTime;
			p->color.g = m_gMin + ( m_gMax - m_gMin ) * normTime;
			p->color.b = m_bMin + ( m_bMax - m_bMin ) * normTime;
		}
	}
}

// -----------------------------------------------
//  SizeAffector
// -----------------------------------------------
void SizeAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->alive ) {
			float normTime = p->timer / p->ttl;
			float size = m_Min + ( m_Max - m_Min ) * normTime;
			p->size.x = size;
			p->size.y = size;
			p->size.z = size;
		}
	}
}

// -----------------------------------------------
//  SizeSineAffector
// -----------------------------------------------
void SizeSineAffector::update(Particles& particles,float elapsed) {
	for ( uint32 i = 0; i < particles.num();++i ) {
		Particle* p = &particles[i];
		if ( p->alive ) {
			float normTime = p->timer / p->ttl;
			float angle = D3DX_PI * normTime;
			float size = m_Min + sin(angle * m_Multiplier) * m_Amplitude;
			p->size.x = size;
			p->size.y = size;
			p->size.z = size;
		}
	}
}
// -----------------------------------------------
//  ParticleSystem
// -----------------------------------------------
ParticleSystem::ParticleSystem(const std::string& name) : m_Name(name) , m_ExternalEmitter(false) {
}


ParticleSystem::~ParticleSystem() {
	if ( !m_ExternalEmitter ) {
		delete m_Emitter;
	}
}

void ParticleSystem::start(const Vec3& startPosition) {
	if ( m_Emitter != 0 ) {
		LOGC(logINFO,"ParticleManager") << "emitting particles at " << startPosition.x << " " << startPosition.y;
		m_Emitter->createParticles(startPosition,m_Particles);
	}
}

void ParticleSystem::update( float elapsed ) {
	for ( size_t i = 0; i < m_Affectors.num(); ++i ) {
		ParticleAffector* affector = m_Affectors[i];
		affector->update(m_Particles,elapsed);
	}
	// clean up or  reset emitted flag
	uint32 cnt = 0;
	while ( cnt < m_Particles.num() ) {
		Particle* p = &m_Particles[cnt];
		if ( p->emitted == true ) {
			p->emitted = false;
		}
		if ( !p->alive ) {
			m_Particles.removeIndex(cnt);
		}
		else {			
			++cnt;
		}
	}
}

void ParticleSystem::fillBuffer(ColouredScreenQuad* particleBuffer) {
	for ( uint32 i = 0; i < m_Particles.num();++i ) {
		Particle* p = &m_Particles[i];
		particleBuffer->add(p->position.x,p->position.y,p->size.x,p->size.y,p->textureRect,p->color,p->angle);
	}
}


// -----------------------------------------------
//  ParticleManager
// -----------------------------------------------
ParticleManager::ParticleManager(const std::string& name,const std::string& textureName,const std::string& layerName) 
	: ds::ColouredScreenQuad("Particles",name+"Material",8192,true) 
	, m_Name(name) 
	, m_TextureName(textureName) 
	, m_LayerName(layerName) {
}

void ParticleManager::loadContent( ResManager& resourceManager ) {
	LOGC(logINFO,"ParticleManager") << "loading content";
	std::string materialName = m_Name + "Material";
	ResourceHandle mh = resourceManager.createMaterial(materialName.c_str(),m_TextureName.c_str(),true);
	std::string shaderName = m_Name+"Shader";
	TransformedTextureColorShader* shader = new TransformedTextureColorShader(shaderName.c_str(),m_TextureName);
	ResourceHandle sh = resourceManager.addShader(shaderName.c_str(),shader);
	resourceManager.assignShader(sh,mh);
	setMaterialName(materialName);
}

ParticleSystem* ParticleManager::prepare(const std::string& systemName) {
	LOGC(logINFO,"ParticleManager") << "preparing new particle system " << systemName;
	ParticleSystemDecl* decl = static_cast<ParticleSystemDecl*>(gEngine->getDeclarationManager().getDeclaration(systemName));
	if ( decl != 0 ) {
		ParticleSystem* system = decl->create();	
		return system;
	}
	else {
		LOGC(logERROR,"ParticleManager") << "no matching declaration found";
	}
	return 0;
}

void ParticleManager::start(const Vec3& startPosition,ParticleSystem* particleSystem) {
	LOGC(logINFO,"ParticleManager") << "starting a prepared particle system";
	m_Systems.append(particleSystem);
	particleSystem->start(startPosition);
}

void ParticleManager::start(const Vec3& startPosition,const std::string& systemName) {
	LOGC(logINFO,"ParticleManager") << "starting new particle system " << systemName;
	ParticleSystem* system = prepare(systemName);
	if ( system != 0 ) {
		start(startPosition,system);
	}	
}

void ParticleManager::update( float elapsed ) {
	uint32 cnt = 0;
	while ( cnt < m_Systems.num()  ) {
		ParticleSystem* system = m_Systems[cnt];	
		system->update(elapsed);	
		if ( !system->isAlive() ) {
			LOGC(logINFO,"ParticleManager") << "Particle system has died - removing it";
			delete system;
			m_Systems.removeIndex(cnt);
		}
		else {
			++cnt;
		}
	}	
	clear();
	for ( size_t i = 0; i < m_Systems.num(); ++i ) {
		ParticleSystem* system = m_Systems[i];
		system->fillBuffer(this);	
	}	
	rebuild();
}

}
